/*
 * Copyright 2010-2012 Susanta Tewari. <freecode4susant@users.sourceforge.net>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package coalescent.br;

import coalescent.br.BRSPopGen.Tracker;
import coalescent.data.KC64Data;
import coalescent.data.MutablePopGenData;
import coalescent.model.KC64;
import coalescent.model.KC64.EventType;

import commons.core.Mutable;
import commons.core.Statistic;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 * Recursion for a configuration (statistic) based on the infinite-alleles-data.
 *
 * @param <T>
 * @param <A>
 * @param <M>
 * @param <D>
 * @param <S>
 * @param <R>
 * @author Susanta Tewari
 */
public abstract class BRSKC64<T extends Tracker<A, EventType, M, D, S, R>, A, M extends KC64,
                              D extends KC64Data<A, M, D>, S extends Statistic<D, S>,
                              R extends BRSKC64<T, A, M, D, S, R>>
        extends BRSPopGen<T, A, EventType, M, D, S, R> {

    /**
     * Constructs ...
     *
     *
     * @param statistic
     */
    public BRSKC64(S statistic) {
        super(statistic);
    }

    /**
     * Method description
     *
     *
     * @param allele
     *
     * @return
     */
    protected abstract T createTrackerInfoForCoalescent(A allele);


    // <editor-fold defaultstate="collapsed" desc="Impl: eventProb">

    /**
     * Method description
     *
     *
     * @param type
     * @param ancestralConfig
     *
     * @return
     */
    @Override
    protected final BigDecimal eventProb(EventType type, R ancestralConfig) {

        switch (type) {

            case COALESCENT :
                return coalescentProb();

            case MUTATION :
                return mutationProb();

            default :
                throw new RuntimeException("Unknown event type: " + type);
        }
    }

    /**
     * The probability of observing a coalescent event next among the competing transitionTypes in the model
     * in a sample of given size. It is given by the following formula.
     * (n - 1) / ( n - 1 + theta ) (n = sample size)
     *
     * @return the probability of observing a coalescent event next
     */
    private BigDecimal coalescentProb() {

        int n        = getStatistic().getData().getSampleSize();
        double theta = getStatistic().getData().getModel().getMutationRate();

        return new BigDecimal(((n - 1) / (n - 1 + theta)));
    }

    /**
     * The probability of observing a mutation event next among the competing transitionTypes in the model in
     * a sample of given size. It is given by the following formula.
     * theta / ( n - 1 + theta ) (n = sample size)
     * Note that, this method cannot be extracted to <code>MutationModel</code> as the probability
     * depends on all the competing forces.
     *
     * @return the probability of observing a mutation event next
     */
    private BigDecimal mutationProb() {

        int n        = getStatistic().getData().getSampleSize();
        double theta = getStatistic().getData().getModel().getMutationRate();

        return new BigDecimal((theta / (n - 1 + theta)));
    }


    // </editor-fold>
    // <editor-fold defaultstate="collapsed" desc="Impl: cndAncestralProb">

    /**
     * Method description
     *
     *
     * @param type
     * @param ancestralConfig
     *
     * @return
     */
    @Override
    protected final BigDecimal cndAncestralProb(EventType type, R ancestralConfig) {

        switch (type) {

            case COALESCENT :
                return cndAncestralProbCoalescent(ancestralConfig);

            case MUTATION :
                return cndAncestralProbMutation(ancestralConfig);

            default :
                throw new IllegalArgumentException("Unsupported event type: " + type);
        }
    }

    /**
     * Method description
     *
     *
     * @param ancestralConfig
     *
     * @return
     */
    private BigDecimal cndAncestralProbCoalescent(R ancestralConfig) {

        A allele       = ancestralConfig.getTracker().getAllele();
        BigDecimal num = new BigDecimal(getStatistic().getData().getAlleleFrequency(allele) - 1);
        BigDecimal den = new BigDecimal(getStatistic().getData().getSampleSize() - 1);

        return num.divide(den);
    }

    /**
     * Conditional probability that this recursion configuration generated the given ancestral
     * configuration for a mutation event type. For infinite-alleles model this probability is 1.0.
     * This is because the alleles cannot be traced and each mutation creates an unique allele.
     * Note that for infinite-sites model alleles can be traced back as the location of mutation is
     * known.
     *
     * @param ancestralConfig recursion ancestral configuration
     * @return the conditional probability
     */
    protected BigDecimal cndAncestralProbMutation(R ancestralConfig) {
        return BigDecimal.valueOf(1.0);
    }    // </editor-fold>


    // <editor-fold defaultstate="collapsed" desc="Impl: transitions">

    /**
     * Method description
     *
     *
     * @param type
     *
     * @return
     */
    @Override
    public final Iterable<R> transitions(EventType type) {

        if (type == null) {
            throw new NullPointerException("type==null");
        }

        switch (type) {

            case COALESCENT :
                return new Iterable<R>() {

                    @Override
                    public Iterator<R> iterator() {
                        return new CoalescentIterator();
                    }
                };

            case MUTATION :
                return new Iterable<R>() {

                    @Override
                    public Iterator<R> iterator() {
                        return new MutationIterator();
                    }
                };

            default :
                throw new IllegalArgumentException("Unsupported event type: " + type);
        }
    }

    /**
     * This changes the underlying mutable data.
     *
     * @param allele allele
     * @return a new instance of the ancestral configuration as mutation is applied on {@code allele}
     */
    protected abstract T applyMutation(A allele);


    // </editor-fold>

    /**
     * Class description
     *
     *
     * @param <A>
     * @param <D>
     * @param <S>
     * @param <R>
     *
     * @version        Enter version here..., 12/10/09
     * @author         Susanta Tewari
     */
    public static abstract class BRSKC64Impl<A, D extends KC64Data<A, KC64, D>,
            S extends Statistic<D, S>, R extends BRSKC64Impl<A, D, S, R>>
            extends BRSKC64<Tracker<A, D, S, R>, A, KC64, D, S, R> {

        /**
         * Constructs ...
         *
         *
         * @param statistic
         */
        public BRSKC64Impl(S statistic) {
            super(statistic);
        }

        /**
         * Method description
         *
         *
         * @param allele
         *
         * @return
         */
        @Override
        protected final Tracker<A, D, S, R> createTrackerInfoForCoalescent(A allele) {
            return new Tracker<>(allele, EventType.COALESCENT, reflect());
        }

        /**
         * Method description
         *
         *
         * @param allele
         *
         * @return
         */
        @Override
        protected final Tracker<A, D, S, R> applyMutation(A allele) {

            D data                                    = getStatistic().getData();
            MutablePopGenData<A, KC64, D> mutableData = data.getMutable();

            mutableData.removeAllele(allele);

            return new Tracker<>(allele, EventType.MUTATION, reflect());
        }
    }

    /**
     * Class description
     *
     *
     * @version        Enter version here..., 12/10/09
     * @author         Susanta Tewari
     */
    @SuppressWarnings("PackageVisibleField")
    private class CoalescentIterator implements Iterator<R> {

        /** Field description */
        private int i = -1;
        List<A> alleles;

        /**
         * Constructs ...
         *
         */
        public CoalescentIterator() {

            alleles = new ArrayList<>(getStatistic().getData().getAlleles(EventType.COALESCENT));
            i       = alleles.size() - 1;
        }

        /**
         * Method description
         *
         *
         * @return
         */
        @Override
        public boolean hasNext() {
            return i >= 0;
        }

        /**
         * Method description
         *
         *
         * @return
         */
        @Override
        @SuppressWarnings("ValueOfIncrementOrDecrementUsed")
        public R next() {

            A allele = alleles.get(i);


            // freq of allele goes down by 1
            MutablePopGenData<A, M, D> mutableData = getStatistic().getData().getMutable();

            mutableData.setAlleleFrequency(allele,
                                           getStatistic().getData().getAlleleFrequency(allele) - 1);

            R clone = factory(getStatistic().factory(mutableData.getImmutable()));

            clone.setTracker(createTrackerInfoForCoalescent(allele));
            alleles.remove(i--);

            return clone;
        }

        /**
         * Method description
         *
         */
        @Override
        public void remove() {
            throw new UnsupportedOperationException("Remove operation is not supported.");
        }
    }

    /**
     * Class description
     *
     *
     * @version        Enter version here..., 12/10/09
     * @author         Susanta Tewari
     */
    @SuppressWarnings("PackageVisibleField")
    private class MutationIterator implements Iterator<R> {

        /** Field description */
        private int i = -1;
        List<A> alleles;

        /**
         * Constructs ...
         *
         */
        public MutationIterator() {

            alleles = new ArrayList<>(getStatistic().getData().getAlleles(EventType.MUTATION));
            i       = alleles.size() - 1;
        }

        /**
         * Method description
         *
         *
         * @return
         */
        @Override
        public boolean hasNext() {
            return i >= 0;
        }

        /**
         * This method should be analyzed for a possible synchronization issue recursion is
         * implemented in a multi-threaded manner. applyMutation modifies mutable data and the 3rd
         * line get an immutable version from it. During this time if the mutable data is changed by
         * some other thread, then the tracker and the mutable data and hence the immutable version
         * of it would become out of sync. This is because the mutable version is a singleton for
         * every immutable instance.
         *
         * @return
         */
        @Override
        public synchronized R next() {

            A allele               = alleles.get(i);
            T tracker              = applyMutation(allele);
            Mutable<D> mutableData = getStatistic().getData().getMutable();
            R clone                = factory(getStatistic().factory(mutableData.getImmutable()));

            clone.setTracker(tracker);
            alleles.remove(i--);

            return clone;
        }

        /**
         * Method description
         *
         */
        @Override
        public void remove() {
            throw new UnsupportedOperationException("Remove operation is not supported.");
        }
    }

    /**
     * Class description
     *
     *
     * @param <A>
     * @param <D>
     * @param <S>
     * @param <R>
     *
     * @version        Enter version here..., 12/10/09
     * @author         Susanta Tewari
     */
    @SuppressWarnings("ProtectedInnerClass")
    protected static class Tracker<A, D extends KC64Data<A, KC64, D>, S extends Statistic<D, S>,
                                   R extends BRSKC64Impl<A, D, S, R>>
            extends BRSPopGen.Tracker<A, EventType, KC64, D, S, R> {

        /**
         * Constructs ...
         *
         *
         * @param allele
         * @param eventType
         * @param producer
         */
        protected Tracker(A allele, EventType eventType, R producer) {
            super(allele, eventType, producer);
        }
    }
}
